/*
 This file 'cron' is part of Firebird Integrated Solution 1.0

 Copyright (c) 2019 Lincong

 Contact:  
        Email: lincong1987@gmail.com

        QQ: 159257119
 
 See Usage at http://www.jplatformx.com/firebird

 Create date: 2019-10-21 16:01
 */

define(function (require, exports, module) {

	// "use strict";


	/**
	 *
	 * 支持以下表达式
	 * 通配： *
	 * 区间： 1-3,5
	 * 步进： *\/2
	 *
	 *
	 * cron值的范围，这个可能与标准的有一些差别：
	 * Seconds: 0-59
	 * Minutes: 0-59
	 * Hours: 0-23
	 * Day of Month: 1-31
	 * Months: 0-11 (Jan-Dec)
	 * Day of Week: 0-6 (Sun-Sat)
	 *
	 * @example
	 * new CronJob('* * * * * *', function(){
			console.log('当前时间:'+ new Date());
		});
	 *
	 * @description
	 * 在线生成表达式 http://cron.qqe2.com/
	 *
	 * @type {CronJob}
	 */
	module.exports = (function () {


		function CronTime(time) {

			this.source = time;

			this.map = ['second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek'];
			this.constraints = [[0, 59], [0, 59], [0, 23], [1, 31], [0, 11], [1, 7]];
			this.aliases = {
				jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6, jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12,
				sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6
			};

			this.second = {};
			this.minute = {};
			this.hour = {};
			this.dayOfMonth = {};
			this.month = {};
			this.dayOfWeek = {};

			this._parse();

		}

		CronTime.prototype = {
			_parse: function () {

				var aliases = this.aliases,
					source = this.source.replace(/[a-z]/i, function (alias) {

						alias = alias.toLowerCase();

						if (alias in aliases) {
							return aliases[alias];
						}

						throw new Error('Unknown alias: ' + alias);

					}),
					split = this.source.replace(/^\s\s*|\s\s*$/g, '').split(/\s+/),
					cur, len = 6;

				while (len--) {
					cur = split[len] || '*';
					this._parseField(cur, this.map[len], this.constraints[len]);
				}

			},
			_parseField: function (field, type, constraints) {

				var rangePattern = /(\d+?)(?:-(\d+?))?(?:\/(\d+?))?(?:,|$)/g,
					typeObj = this[type],
					diff,
					low = constraints[0],
					high = constraints[1];

				// * is a shortcut to [lower-upper] range
				field = field.replace(/\*/g, low + '-' + high);

				if (field.match(rangePattern)) {

					field.replace(rangePattern, function ($0, lower, upper, step) {

						step = step || 1;

						// Positive integer higher than constraints[0]
						lower = Math.max(low, ~~Math.abs(lower));

						// Positive integer lower than constraints[1]
						upper = upper ? Math.min(high, ~~Math.abs(upper)) : lower;

						diff = step + upper - lower;

						while ((diff -= step) > -1) {
							typeObj[diff + lower] = true;
						}

					});

				} else {

					throw new Error('Field (' + field + ') cannot be parsed');

				}

			}
		};

		function CronJob(cronTime, event) {

			if (!(this instanceof CronJob)) {
				return new CronJob(cronTime, event);
			}

			this.events = [event];
			this.cronTime = new CronTime(cronTime);
			this.now = {};
			this.initiated = false;

			this.clock();

		}

		CronJob.prototype = {

			addEvent: function (event) {
				this.events.push(event);
			},

			runEvents: function () {
				for (var i = -1, l = this.events.length; ++i < l;) {
					if (typeof this.events[i] === 'function') {
						this.events[i]();
					}
				}
			},

			clock: function () {

				var date = new Date,
					now = this.now,
					self = this,
					cronTime = this.cronTime,
					i;

				if (!this.initiated) {
					// Make sure we start the clock precisely ON the 0th millisecond
					setTimeout(function () {
						self.initiated = true;
						self.clock();
					}, Math.ceil(+date / 1000) * 1000 - +date);
					return;
				}

				this.timer = this.timer || setInterval(function () {
					self.clock();
				}, 1000);

				now.second = date.getSeconds();
				now.minute = date.getMinutes();
				now.hour = date.getHours();
				now.dayOfMonth = date.getDate();
				now.month = date.getMonth();
				now.dayOfWeek = date.getDay();

				for (i in now) {
					if (!(now[i] in cronTime[i])) {
						return;
					}
				}

				this.runEvents();

			}

		};

		return CronJob;

	})();
});